import java.io.*;

public class SharedObject implements Serializable, SharedObject_itf {
	private int id;
	public Object obj;
	private MyLock lock = MyLock.NL;
	private boolean locked = false;

	public SharedObject() {
		this.obj = null;
	}

	public SharedObject(Object obj) {
		this.obj = obj;
	}

	// invoked by the user program on the client node
	/**
	 * call the client to turn the lock_read on
	 */
	public void lock_read() {
		synchronized(this.lock) {
			if(this.lock == MyLock.RLC) {
				//System.out.println("Client.lock_read() local");
				this.lock = MyLock.RLT;
				Client.maj();
				locked = true;
				return;
			} else if(this.lock == MyLock.WLC) {
				//System.out.println("Client.lock_read() local");
				this.lock = MyLock.RLT_WLC;
				Client.maj();
				locked = true;
				return;
			}
		}
		this.obj = Client.lock_read(this.id);
		//System.out.println("Client.lock_read()");
		this.lock = MyLock.RLT;
		Client.maj();
	}

	// invoked by the user program on the client node
	/**
	 * call the client to turn the lock_write on
	 */
	public void lock_write() {
		synchronized(this.lock) {
			if(this.lock == MyLock.WLC || this.lock == MyLock.RLT_WLC) {
				//System.out.println("Client.lock_write() local");
				this.lock = MyLock.WLT;
				Client.maj();
				locked = true;
				return;
			}else if(this.lock == MyLock.WLT) {
				System.err.println("Error: SharedObject.lock_write() while in WLT state!");
				return;
			}
		}
		this.obj = Client.lock_write(this.id);
		//System.out.println("Client.lock_write()");
		this.lock = MyLock.WLT;
		Client.maj();
	}

	// invoked by the user program on the client node
	/**
	 * call the client to turn the lock off
	 */
	public synchronized void unlock() {
		//System.out.println("Client.unlock()");
		if(this.lock == MyLock.RLT) {
			this.lock = MyLock.RLC;
		} else if(this.lock == MyLock.WLT || this.lock == MyLock.RLT_WLC) {
			this.lock = MyLock.WLC;
		} else {
			//System.out.println("No need to unlock because it's already in the state " + this.lock);	//NL, RLC or WLC
		}
		this.locked = false;
		Client.maj();
		this.notify();
	}

	// callback invoked remotely by the server
	/**
	 * if the SharedObject is on WL
	 * turn the SharedObject on RL
	 */
	public synchronized Object reduce_lock() {
		synchronized(this.lock) {
			while(this.lock == MyLock.NL || this.lock == MyLock.WLT) {
				//System.out.println("Asking Client.reduce_lock()");
				try {
					this.wait();
				} catch (Exception exc) {
					exc.printStackTrace();
				}
			}
			//System.out.println("Client.reduce_lock()");
			if(this.lock == MyLock.WLC) {
				this.lock = MyLock.RLC;
			} else if(this.lock == MyLock.RLT_WLC) {
				this.lock = MyLock.RLT;
			} else {
				//this.lock == MyLock.RLT or MyLock.TLC, it's OK
			}
			Client.maj();
			return this.obj;
		}
	}

	// callback invoked remotely by the server
	/**
	 * if the SharedObject is on RL
	 * turn the SharedObject on NL
	 */
	public synchronized void invalidate_reader() {
		synchronized(this.lock) {
			while(this.locked || this.lock == MyLock.NL || this.lock == MyLock.RLT) {
				//System.out.println("Asking Client.invalidate_reader()");
				try {
					this.wait();
				} catch (Exception exc) {
					exc.printStackTrace();
				}
			}
			//System.out.println("Client.invalidate_reader()");
			this.lock = MyLock.NL;
			this.locked = true;
			Client.maj();
		}
	}

	/**
	 * if the SharedObject is on WL
	 * turn the SharedObject on NL
	 */
	public synchronized Object invalidate_writer() {
		synchronized(this.lock) {
			while(this.locked || this.lock == MyLock.NL || this.lock == MyLock.WLT || this.lock == MyLock.RLT_WLC) {
				//System.out.println("Asking Client.invalidate_writer()");
				try {
					this.wait();
				} catch (Exception exc) {
					exc.printStackTrace();
				}
			}
			//System.out.println("Client.invalidate_writer()");
			if(this.lock == MyLock.NL || this.lock == MyLock.RLC || this.lock == MyLock.RLT)
				System.err.println("Error: SharedObject.invalidate_writer() while in " + this.lock);	//NL, RLC or RLT
			this.lock = MyLock.NL;
			this.locked = true;
			Client.maj();
			return this.obj;
		}
	}

	public int getId() {
		return this.id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public MyLock getLock() {
		return this.lock;
	}

	public Object readResolve() {
		System.out.println("called: SharedObject.readResolve() for " + this.getClass().getName());
		SharedObject so = Client.ids.get(this.id);
		if(so != null) {
			return so;
		} else {
			lock = MyLock.NL;
			Client.ids.put(this.id, this);
			return this;
		}
	}

}
